using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Perovich.GameObjects;
using System.Collections.ObjectModel;
using Perovich.GameObjects.Interfaces;

namespace Perovich.GameObjects.ThreeDimensional
{
    /// <summary>
    /// The <see cref="InteractiveObject3D"/> class is a <see cref="ModelObject3D"/> that supports collision detection with other <see cref="InteractiveObject3D"/>s.
    /// </summary>
    /// <remarks>
    /// The <see cref="InteractiveObject3D.Collided"/> event is deterministicly tirggered after all update events have been processed in the game loop.
    /// </remarks>
    public class InteractiveObject3D : ModelObject3D, ICollidable
    {

        /// <summary>
        /// Gets or sets the offset of this <see cref="InteractiveObject3D"/>'s <see cref="Microsoft.Xna.Framework.BoundingSphere"/> used for collision detection.
        /// </summary>
        public Vector3 BoundingSphereOffset
        {
            get;
            set;
        }


        /// <summary>
        /// Gets or sets the scale applied to this <see cref="InteractiveObject3D"/>'s <see cref="Microsoft.Xna.Framework.BoundingSphere"/> after calculating the <see cref="Microsoft.Xna.Framework.BoundingSphere"/> from the <see cref="ModelObject3Dbase.ObjectModel"/>.
        /// </summary>
        public float BoundingSphereScale
        {
            get;
            set;
        }


        /// <summary>
        /// Gets or sets a value indicating whether the <see cref="InteractiveObject3D"/> draws its <see cref="Microsoft.Xna.Framework.BoundingSphere"/>.
        /// </summary>
        /// <value>true if the <see cref="Microsoft.Xna.Framework.BoundingSphere"/> is to be drawn; false otherwise</value>
        public bool DrawBoundingSphere
        {
            get
            {
                return bSphere.Active;
            }
            set
            {
                bSphere.Active = value;
            }
        }


        /// <summary>
        /// Constructs a new instance of the <see cref="InteractiveObject3D"/> class.
        /// </summary>
        /// <param name="game"><see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> instance that this <see cref="DrawableComponent"/> will be constructed for.</param>
        public InteractiveObject3D(Interfaces.ICanHoldComponents game)
            : base(game)
        {

            bSphere = new WireframeModelObject3D(game);
            bSphere.Active = false;
            BoundingSphereOffset = Vector3.Zero;
            BoundingSphereScale = 1;

            CameraAdded += new EventHandler<CameraAddedEventArgs>(InteractiveObject3D_CameraAdded);
            ModelChanged += new EventHandler(InteractiveObject3D_ModelChanged);
            PositionChanged += new EventHandler(InteractiveObject3D_PositionChanged);
            ActiveChanged += new EventHandler(InteractiveObject3D_ActiveChanged);

        }


        void InteractiveObject3D_ActiveChanged(object sender, EventArgs e)
        {
            if (DrawBoundingSphere)
            {
                bSphere.Active = Active;
            }
        }

        void InteractiveObject3D_PositionChanged(object sender, EventArgs e)
        {
            BoundingSphere = CalculateBoundingSphere();
        }

        void InteractiveObject3D_ModelChanged(object sender, EventArgs e)
        {
            BoundingSphere = CalculateBoundingSphere();
        }

        void InteractiveObject3D_CameraAdded(object sender, CameraAddedEventArgs e)
        {
            bSphere.AddCameras(e.AddedCamera);
        }


        WireframeModelObject3D bSphere;

        /// <summary>
        /// Gets the <see cref="Microsoft.Xna.Framework.BoundingSphere"/> this <see cref="InteractiveObject3D"/> uses for collision detection.
        /// </summary>\
        /// <value>A <see cref="Microsoft.Xna.Framework.BoundingSphere"/> representing the <see cref="InteractiveObject3D"/> in collisions.</value>
        public BoundingSphere CollisionSphere
        {
            get
            {
                return BoundingSphere;
            }
        }


        public dynamic CollisionSolid
        {
            get
            {
                return CollisionSphere;
            }
        }


        /// <summary>
        /// Gets the <see cref="Microsoft.Xna.Framework.BoundingSphere"/> this <see cref="InteractiveObject3D"/> uses for collision detection.
        /// </summary>\
        /// <value>A <see cref="Microsoft.Xna.Framework.BoundingSphere"/> representing the <see cref="InteractiveObject3D"/> in collisions.</value>
        public BoundingSphere BoundingSphere
        {
            get;
            set;
        }


        /// <summary>
        /// Calculates the <see cref="Microsoft.Xna.Framework.BoundingSphere"/> to use for this <see cref="InteractiveObject3D"/> in collisions.
        /// </summary>
        /// <returns>A <see cref="Microsoft.Xna.Framework.BoundingSphere"/> representing the <see cref="InteractiveObject3D"/> in collisions.</returns>
        /// <remarks>Derived classes can override this method to provide a different <see cref="Microsoft.Xna.Framework.BoundingSphere"/> calculation algorithm.</remarks>
        protected virtual BoundingSphere CalculateBoundingSphere()
        {
            BoundingSphere mergedSphere;
            BoundingSphere[] boundingSpheres;
            int index = 0;
            int meshCount = ObjectModel.Meshes.Count;

            boundingSpheres = new BoundingSphere[meshCount];
            foreach (ModelMesh mesh in ObjectModel.Meshes)
            {
                boundingSpheres[index++] = mesh.BoundingSphere;
            }

            mergedSphere = boundingSpheres[0];
            if ((ObjectModel.Meshes.Count) > 1)
            {
                index = 1;
                do
                {
                    mergedSphere = BoundingSphere.CreateMerged(mergedSphere,
                        boundingSpheres[index]);
                    index++;
                } while (index < ObjectModel.Meshes.Count);
            }
            mergedSphere.Center = Position + BoundingSphereOffset;
            mergedSphere.Radius *= BoundingSphereScale;
            bSphere.Scale = mergedSphere.Radius;
            bSphere.Position = mergedSphere.Center;
            return mergedSphere;
        }


        /// <summary>
        /// Allows the <see cref="InteractiveObject3D"/> to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of the game's timing values.</param>
        public override void Update(GameTime gameTime)
        {
            bSphere.Scale = BoundingSphere.Radius;
            bSphere.Position = BoundingSphere.Center;
            base.Update(gameTime);
        }


        List<InteractiveObject3D> _collisionTargets = new List<InteractiveObject3D>();

        /// <summary>
        /// Gets the list of <see cref="InteractiveObject3D"/>s this <see cref="InteractiveObject3D"/> collides with.
        /// </summary>
        /// <value>A <see cref="ReadOnlyCollection{InteractiveObject3D}"/> containing all the <see cref="InteractiveObject3D"/>s this <see cref="InteractiveObject3D"/> collides with.</value>
        public ReadOnlyCollection<InteractiveObject3D> CollisionTargets
        {
            get
            {
                return new ReadOnlyCollection<InteractiveObject3D>(_collisionTargets);
            }
        }

        /// <summary>
        /// Triggered when a collision is detected with any of the <see cref="InteractiveObject3D"/>s in the <see cref="CollisionTargets"/> collection.
        /// </summary>
        public event EventHandler<CollisionEventArgs> Collided;

        /// <summary>
        /// Adds one or more <see cref="InteractiveObject3D"/>s to this instances <see cref="CollisionTargets"/> collection.
        /// </summary>
        /// <param name="targets">The <see cref="InteractiveObject3D"/>s to add.</param>
        public void AddCollisionTargets(params InteractiveObject3D[] targets)
        {
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (InteractiveObject3D obj in targets)
            {
                _collisionTargets.Add(obj);
            }
        }


        /// <summary>
        /// Adds one or more <see cref="InteractiveObject3D"/>s to this instances <see cref="CollisionTargets"/> collection.
        /// </summary>
        /// <param name="targets">The <see cref="InteractiveObject3D"/>s to add.</param>
        public void AddCollisionTargets(IEnumerable<InteractiveObject3D> targets)
        {
            if (targets == null)
                throw new ArgumentNullException("targets");
            foreach (InteractiveObject3D obj in targets)
            {
                _collisionTargets.Add(obj);
            }
        }

        void ICollidable.CheckCollisions()
        {
            if (Active)
            {
                foreach (InteractiveObject3D other in CollisionTargets)
                {
                    if (CollisionSphere.Intersects(other.CollisionSphere))
                    {
                        SignalCollision(other);
                    }
                }
            }
        }



        private void SignalCollision(InteractiveObject3D other)
        {
            if (Collided != null)
            {
                Collided(this, new CollisionEventArgs(other));
            }
        }

    }
}
